TI_GDT equ 0
RPL_0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL_0

[bits 32]
section .text


global put_char,put_str,set_cursor_pos

put_str:
    push eax
    push ecx

    mov ecx,[esp + 4 * 2 + 4]  ; target str address
    xor eax,eax
.display_char:
    mov al,[ecx]
    cmp al,0x00
    jz .put_str_end
    push eax
    call put_char
    inc ecx
    add esp,4
jmp .display_char
.put_str_end:
    pop ecx
    pop eax
    ret

put_char:
    pushad
    mov ax,SELECTOR_VIDEO
    mov gs,ax

    ;read cursor location high register
    mov dx,0x03d4
    mov al, 0x0e
    out dx,al
    mov dx,0x03d5
    in al,dx
    mov ah,al

    ;read cursor location low register
    mov dx,0x03d4
    mov al,0x0f
    out dx,al
    mov dx,0x03d5
    in al,dx
    mov bx,ax           ;bx is the cursor's current location


    mov ecx,[esp + 4 * 8 + 4]  ; target char
    cmp cl,0x08
    jz .backspace
    cmp cl, 0xd				                                ;判断是否是CR(回车)0x0d
    jz .is_carriage_return
    cmp cl, 0xa                                             ;判断是否是LF(换行)0x0a
    jz .is_line_feed


    jmp .set_other

.backspace:
    dec bx
    shl bx,1
    mov byte [gs:bx],0x20
    inc bx
    mov byte [gs:bx],0x07
    shr bx,1
    jmp .set_cursor


.set_other:
    shl bx,1
    mov byte [gs:bx],cl
    inc bx
    mov byte [gs:bx],0x07
    shr bx,1
    inc bx
    cmp bx,2000
    jl .set_cursor

 .is_line_feed:				                                ; 是换行符LF(\n)
 .is_carriage_return:			                            ; 是回车符CR(\r)
					                                        ; 如果是CR(\r),只要把光标移到行首就行了。
    xor dx, dx				                                ;要进行16位除法，高16位置会放在dx中，要先清零
    mov ax, bx				                                ;ax是被除数的低16位.
    mov si, 80				                                ;用si寄存器来存储除数80 由于是效仿linux，linux中\n便表示下一行的行首，所以本系统中，
    div si				                                    ; 把\n和\r都处理为linux中\n的意思，也就是下一行的行首。ax/80后，ax中存商，dx中存储的是余数,汇编除法https://blog.csdn.net/loovejava/article/details/7044242
    sub bx, dx				                                ; 光标值减去除80的余数便是取整
					                                        ; 以上4行处理\r的代码
.is_carriage_return_end:		                            ; 回车符CR处理结束
    add bx, 80
    cmp bx, 2000
.is_line_feed_end:			                                ; 若是LF(\n),将光标移+80便可。  
    jl .set_cursor

.roll_screen:				                                ; 若超出屏幕大小，开始滚屏
    cld                                                     
    mov ecx, 960				                            ; 一共有2000-80=1920个字符要搬运,共1920*2=3840字节.一次搬4字节,共3840/4=960次 
    mov esi, 0xb80a0			                            ; 第1行行首
    mov edi, 0xb8000			                            ; 第0行行首
    rep movsd				                                ;rep movs word ptr es:[edi], word ptr ds:[esi] 简写为: rep movsw

                                                            ;将最后一行填充为空白
    mov ebx, 3840			                                ; 最后一行首字符的第一个字节偏移= 1920 * 2
    mov ecx, 80				                                ;一行是80字符(160字节),每次清空1字符(2字节),一行需要移动80次
 .cls:
    mov word [gs:ebx], 0x0720		                        ;0x0720是黑底白字的空格键
    add ebx, 2
    loop .cls 
    mov bx,1920				                                ;将光标值重置为1920,最后一行的首字符.


.set_cursor:
    ;set cursor location high register
    mov dx,0x03d4
    mov al, 0x0e
    out dx,al
    mov dx,0x03d5
    mov al,bh
    out dx,al
   

    ;set cursor location low register
    mov dx,0x03d4
    mov al,0x0f
    out dx,al
    mov dx,0x03d5
    mov al,bl
    out dx,al

.put_char_end:
popad
ret


section .data
put_int_buffer dq 0                                         ; 定义8字节缓冲区用于数字到字符的转换

global put_int
put_int:
   pushad
   mov ebp, esp
   mov eax, [ebp+4*9]		                                ; call的返回地址占4字节+pushad的8个4字节，现在eax中就是要显示的32位数值
   mov edx, eax                                             ;edx中现在是要显示的32位数值
   mov edi, 7                                               ; 指定在put_int_buffer中初始的偏移量，也就是把栈中第一个字节取出放入buffer最后一个位置，第二个字节放入buff倒数第二个位置
   mov ecx, 8			                                    ; 32位数字中,16进制数字的位数是8个
   mov ebx, put_int_buffer                                  ;ebx现在存储的是buffer的起始地址

                                                            ;将32位数字按照16进制的形式从低位到高位逐个处理,共处理8个16进制数字
.16based_4bits:			                                    ; 每4位二进制是16进制数字的1位,遍历每一位16进制数字
   and edx, 0x0000000F		                                ; 解析16进制数字的每一位。and与操作后,edx只有低4位有效
   cmp edx, 9			                                    ; 数字0～9和a~f需要分别处理成对应的字符
   jg .is_A2F 
   add edx, '0'			                                    ; ascii码是8位大小。add求和操作后,edx低8位有效。
   jmp .store
.is_A2F:
   sub edx, 10			                                    ; A~F 减去10 所得到的差,再加上字符A的ascii码,便是A~F对应的ascii码
   add edx, 'A'

                                                            ;将每一位数字转换成对应的字符后,按照类似“大端”的顺序存储到缓冲区put_int_buffer
                                                            ;高位字符放在低地址,低位字符要放在高地址,这样和大端字节序类似,只不过咱们这里是字符序.
.store:
   mov [ebx+edi], dl		                                ; 此时dl中是数字对应的字符的ascii码
   dec edi                                                  ;edi是表示在buffer中存储的偏移，现在向前移动
   shr eax, 4                                               ;eax中是完整存储了这个32位数值，现在右移4位，处理下一个4位二进制表示的16进制数字
   mov edx, eax                                             ;把eax中的值送入edx，让ebx去处理
   loop .16based_4bits

                                                            ;现在put_int_buffer中已全是字符,打印之前,
                                                            ;把高位连续的字符去掉,比如把字符00000123变成123
.ready_to_print:
   inc edi			                                        ; 此时edi退减为-1(0xffffffff),加1使其为0
.skip_prefix_0:                                             ;跳过前缀的连续多个0
   cmp edi,8			                                    ; 若已经比较第9个字符了，表示待打印的字符串为全0 
   je .full0 
                                                            ;找出连续的0字符, edi做为非0的最高位字符的偏移
.go_on_skip:   
   mov cl, [put_int_buffer+edi]
   inc edi
   cmp cl, '0' 
   je .skip_prefix_0		                                ; 继续判断下一位字符是否为字符0(不是数字0)
   dec edi			                                        ;edi在上面的inc操作中指向了下一个字符,若当前字符不为'0',要恢复edi指向当前字符		       
   jmp .put_each_num

.full0:
   mov cl,'0'			                                    ; 输入的数字为全0时，则只打印0
.put_each_num:
   push ecx			                                        ; 此时cl中为可打印的字符
   call put_char
   add esp, 4
   inc edi			                                        ; 使edi指向下一个字符
   mov cl, [put_int_buffer+edi]	                            ; 获取下一个字符到cl寄存器
   cmp edi,8                                                ;当edi=8时，虽然不会去打印，但是实际上已经越界访问缓冲区了
   jl .put_each_num
   popad
   ret


set_cursor_pos:
    push bx
    mov bx,[esp + 8]
    ;set cursor location high register
    mov dx,0x03d4
    mov al, 0x0e
    out dx,al
    mov dx,0x03d5
    mov al,bh
    out dx,al
   

    ;set cursor location low register
    mov dx,0x03d4
    mov al,0x0f
    out dx,al
    mov dx,0x03d5
    mov al,bl
    out dx,al
    pop bx
    ret